﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Text.RegularExpressions;

using BOSS.Framework.Search.Context;

namespace BOSS.Web.ModelBinders
{
    /// <summary>
    /// 搜索上下文模型绑定器
    /// </summary>
    public class SearchContextModelBinder : ICustomModelBinder
    {
        private static readonly Type[] supportedTypes = new Type[] {
            typeof(ISearchContext),
        };

        /// <summary>
        /// 范围取值验证表达式
        /// </summary>
        private static readonly Regex rangeValueRegex = new Regex(@"(?<imin>[\[\{])(?<min>\w+|-)\s*(TO|-)\s*(?<max>\w+|-)(?<imax>[\}\]])", RegexOptions.Compiled | RegexOptions.IgnoreCase);

        #region ICustomModelBinder 成员

        /// <summary>
        /// 获取 所支持的类型
        /// </summary>
        public IEnumerable<Type> SupportedTypes
        {
            get { return supportedTypes; }
        }

        #endregion

        #region IModelBinder 成员

        /// <summary>
        /// 绑定数据
        /// </summary>
        /// <param name="controllerContext"></param>
        /// <param name="bindingContext"></param>
        /// <returns></returns>
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            IDictionary<string, ValueProviderResult> valueProvider = bindingContext.ValueProvider;
            bool hasPrefix = !string.IsNullOrEmpty(bindingContext.ModelName);
            string prefix = hasPrefix ? bindingContext.ModelName + "." : string.Empty;
            HashSet<string> filterFields = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

            // 遍历相关参数
            foreach (var item in valueProvider)
            {
                string key = item.Key;
                if (hasPrefix && !key.StartsWith(prefix))
                {
                    continue;
                }

                string[] parts = key.Substring(prefix.Length).Split('.');
                string fieldName = parts[0];
                if (parts.Length == 1)
                {
                    if (key.Equals("_pageSize", StringComparison.OrdinalIgnoreCase))
                    {
                        BindPageSize(bindingContext, key, item.Value);
                    }
                    else if (key.Equals("_pageIndex", StringComparison.OrdinalIgnoreCase))
                    {
                        BindPageIndex(bindingContext, key, item.Value);
                    }
                    else
                    {
                        BindTermFilter(bindingContext, key, fieldName, item.Value);
                    }
                }
                else
                {
                    string type = parts[1];
                    if (type.Equals("min", StringComparison.OrdinalIgnoreCase))
                    {
                        if (filterFields.Contains(fieldName))
                        {
                            continue;
                        }
                        filterFields.Add(fieldName);
                        ValueProviderResult maxResult = null;
                        valueProvider.TryGetValue(string.Concat(prefix, fieldName, ".max"), out maxResult);
                        BindRangeFilter(bindingContext, prefix, fieldName, item.Value, maxResult);
                    }
                    else if (type.Equals("max", StringComparison.OrdinalIgnoreCase))
                    {
                        if (filterFields.Contains(fieldName))
                        {
                            continue;
                        }
                        filterFields.Add(fieldName);
                        ValueProviderResult minResult = null;
                        valueProvider.TryGetValue(string.Concat(prefix, fieldName, ".min"), out minResult);
                        BindRangeFilter(bindingContext, prefix, fieldName, minResult, item.Value);
                    }
                    else if (type.Equals("range", StringComparison.OrdinalIgnoreCase))
                    {
                        BindRangeFilter(bindingContext, prefix, fieldName, item.Value);
                    }
                    else if (type.Equals("sort", StringComparison.OrdinalIgnoreCase))
                    {
                        BindSort(bindingContext, key, fieldName, item.Value);
                    }
                    else
                    {
                        if (type.Equals("city", StringComparison.OrdinalIgnoreCase))
                        {
                            BindTermFilter(bindingContext, key, fieldName, item.Value);
                        }
                        else if (type.Equals("province", StringComparison.OrdinalIgnoreCase))
                        {
                            ISearchContext searchContext = (ISearchContext)bindingContext.Model;
                            if (searchContext == null || searchContext.GetFilterTermValueAsInt32(key) == null)
                            {
                                BindTermFilter(bindingContext, key, fieldName, item.Value);
                            }
                        }

                        BindTermFilter(bindingContext, key, string.Concat(fieldName, '.', type), item.Value);
                    }
                }
            }

            return bindingContext.Model;
        }

        #endregion

        /// <summary>
        /// 绑定一个固定值过滤条件
        /// </summary>
        /// <param name="bindingContext"></param>
        /// <param name="key"></param>
        /// <param name="fieldName"></param>
        /// <param name="result"></param>
        private void BindTermFilter(ModelBindingContext bindingContext, string key, string fieldName, ValueProviderResult result)
        {
            if (!string.IsNullOrEmpty(result.AttemptedValue))
            {
                ISearchContext searchContext = (ISearchContext)bindingContext.Model;
                searchContext.SetFilter(fieldName, result.AttemptedValue);
            }
        }

        /// <summary>
        /// 绑定一个范围取值过滤条件
        /// </summary>
        /// <param name="bindingContext"></param>
        /// <param name="prefix"></param>
        /// <param name="fieldName"></param>
        /// <param name="minResult"></param>
        /// <param name="maxResult"></param>
        private void BindRangeFilter(ModelBindingContext bindingContext, string prefix, string fieldName, ValueProviderResult minResult, ValueProviderResult maxResult)
        {
            if (minResult == null)
            {
                minResult = new ValueProviderResult(string.Empty, string.Empty, null);
            }
            if (maxResult == null)
            {
                maxResult = new ValueProviderResult(string.Empty, string.Empty, null);
            }
            bindingContext.ModelState.SetModelValue(prefix + fieldName + ".min", minResult);
            bindingContext.ModelState.SetModelValue(prefix + fieldName + ".max", maxResult);
            BindRangeFilter(bindingContext, prefix, fieldName, minResult.AttemptedValue, true, maxResult.AttemptedValue, true);
        }

        /// <summary>
        /// 绑定一个范围取值过滤条件
        /// </summary>
        /// <param name="bindingContext"></param>
        /// <param name="prefix"></param>
        /// <param name="fieldName"></param>
        /// <param name="result"></param>
        private void BindRangeFilter(ModelBindingContext bindingContext, string prefix, string fieldName, ValueProviderResult result)
        {
            if (string.IsNullOrEmpty(result.AttemptedValue))
            {
                return;
            }

            bindingContext.ModelState.SetModelValue(prefix + fieldName + ".range", result);

            Match match = rangeValueRegex.Match(result.AttemptedValue);
            if (!match.Success)
            {
                bindingContext.ModelState.AddModelError(prefix + fieldName + ".range", "范围过滤条件的格式异常");
                return;
            }

            bool includeMin = false;
            bool includeMax = false;

            switch (match.Groups["imin"].Value)
            {
                case "[":
                    includeMin = true;
                    break;

                case "{":
                    includeMin = false;
                    break;

                default:
                    bindingContext.ModelState.AddModelError(prefix + fieldName + ".range", "范围过滤条件的格式异常");
                    return;
            }

            switch (match.Groups["imax"].Value)
            {
                case "]":
                    includeMax = true;
                    break;

                case "}":
                    includeMax = false;
                    break;

                default:
                    bindingContext.ModelState.AddModelError(prefix + fieldName + ".range", "范围过滤条件的格式异常");
                    return;
            }

            BindRangeFilter(bindingContext, prefix, fieldName, match.Groups["min"].Value, includeMin, match.Groups["max"].Value, includeMax);
        }

        /// <summary>
        /// 绑定一个范围取值过滤条件
        /// </summary>
        /// <param name="bindingContext"></param>
        /// <param name="prefix"></param>
        /// <param name="fieldName"></param>
        /// <param name="minValue"></param>
        /// <param name="includeMin"></param>
        /// <param name="maxValue"></param>
        /// <param name="includeMax"></param>
        private void BindRangeFilter(ModelBindingContext bindingContext, string prefix, string fieldName, string minValue, bool includeMin, string maxValue, bool includeMax)
        {
            DateTime minDate = DateTime.MinValue;
            DateTime maxDate = DateTime.MaxValue;
            decimal minNum = decimal.MinValue;
            decimal maxNum = decimal.MaxValue;

            bool hasError = false;

            if (!string.IsNullOrEmpty(minValue) && minValue != "-" && minValue != "请选择日期")
            {
                if (!DateTime.TryParse(minValue, out minDate))
                {
                    if (!decimal.TryParse(minValue, out minNum))
                    {
                        bindingContext.ModelState.AddModelError(prefix + fieldName + ".min", "范围过滤条件的格式异常");
                        hasError = true;
                    }
                }
            }

            if (!string.IsNullOrEmpty(maxValue) && maxValue != "-" && maxValue != "请选择日期")
            {
                if (!DateTime.TryParse(maxValue, out maxDate))
                {
                    maxDate = DateTime.MaxValue;
                    if (!decimal.TryParse(maxValue, out maxNum))
                    {
                        maxNum = decimal.MaxValue;
                        bindingContext.ModelState.AddModelError(prefix + fieldName + ".max", "范围过滤条件的格式异常");
                        hasError = true;
                    }
                }
            }

            if (hasError)
            {
                return;
            }

            if (minDate != DateTime.MinValue || maxDate != DateTime.MaxValue)
            {
                if (minNum != decimal.MinValue || maxNum != decimal.MaxValue)
                {
                    bindingContext.ModelState.AddModelError(prefix + fieldName + ".max", "范围过滤条件的最小值、最大值类型不一致");
                    return;
                }
                else
                {
                    ISearchContext searchContext = (ISearchContext)bindingContext.Model;
                    searchContext.SetFilter(fieldName, minDate, includeMin, maxDate, includeMax);
                }
            }
            else if (minNum != decimal.MinValue || maxNum != decimal.MaxValue)
            {
                ISearchContext searchContext = (ISearchContext)bindingContext.Model;
                searchContext.SetFilter(fieldName, minNum, includeMin, maxNum, includeMax);
            }
        }

        /// <summary>
        /// 绑定排序条件
        /// </summary>
        /// <param name="bindingContext"></param>
        /// <param name="key"></param>
        /// <param name="fieldName"></param>
        /// <param name="result"></param>
        private void BindSort(ModelBindingContext bindingContext, string key, string fieldName, ValueProviderResult result)
        {
            if (!string.IsNullOrEmpty(result.AttemptedValue))
            {
                bool descend = false;
                if (bool.TryParse(result.AttemptedValue, out descend))
                {
                    ISearchContext searchContext = (ISearchContext)bindingContext.Model;
                    searchContext.SetSort(fieldName, descend);
                }
                else
                {
                    bindingContext.ModelState.SetModelValue(key, result);
                    bindingContext.ModelState.AddModelError(key, "排序方向设置异常");
                }
            }
        }

        /// <summary>
        /// 绑定分页大小
        /// </summary>
        /// <param name="bindingContext"></param>
        /// <param name="key"></param>
        /// <param name="result"></param>
        private void BindPageSize(ModelBindingContext bindingContext, string key, ValueProviderResult result)
        {
            if (!string.IsNullOrEmpty(result.AttemptedValue))
            {
                int pageSize = 0;
                if (int.TryParse(result.AttemptedValue, out pageSize))
                {
                    ISearchContext searchContext = (ISearchContext)bindingContext.Model;
                    searchContext.SetPageSize(pageSize);
                }
                else
                {
                    bindingContext.ModelState.SetModelValue(key, result);
                    bindingContext.ModelState.AddModelError(key, "分页大小设置异常");
                }
            }
        }

        /// <summary>
        /// 绑定页序号
        /// </summary>
        /// <param name="bindingContext"></param>
        /// <param name="key"></param>
        /// <param name="result"></param>
        private void BindPageIndex(ModelBindingContext bindingContext, string key, ValueProviderResult result)
        {
            if (!string.IsNullOrEmpty(result.AttemptedValue))
            {
                int pageIndex = 0;
                if (int.TryParse(result.AttemptedValue, out pageIndex))
                {
                    ISearchContext searchContext = (ISearchContext)bindingContext.Model;
                    searchContext.SetPageIndex(pageIndex);
                }
                else
                {
                    bindingContext.ModelState.SetModelValue(key, result);
                    bindingContext.ModelState.AddModelError(key, "页序号设置异常");
                }
            }
        }
    }
}
